
汇编器解析器很容易实现，LLVM为它提供了一个框架，而且大部分都是从目标描述生成的。

当框架检测到需要解析指令时，就会调用类中的ParseInstruction()方法，通过提供的词法分析器解析输入，并构造一个所谓的操作数向量。操作数可以是标记，如指令助记符、寄存器名或直接对象，也可以是特定于目标的类别。例如，从jmp \%r2输入构造两个操作数:一个用于助记符的标记操作数和一个寄存器操作数。

然后，生成的匹配器尝试将操作数向量与指令进行匹配。若找到匹配项，则创建MCInst类的实例，其中保存解析后的指令；否则，会发出错误消息。这种方法的优点是，可自动地从目标描述中派生出匹配器，不需要处理所有的语法问题。

但我们需要添加更多的支持类，来使汇编解析器工作，这些类都放在MCTargetDesc目录中。


\mySamllsection{实现M88k目标的MCAsmInfo支持类}

本节中，将探讨实现汇编解析器配置所需的第一个类:MCAsmInfo类:

\begin{enumerate}
\item
需要为汇编解析器设置一些自定义参数，MCAsmInfo基类(\url{https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/MC/MCAsmInfo.h})包含常用参数。此外，为每种支持的对象文件格式创建一个子类：例如，MCAsmInfoELF类(\url{https://github.com/llvm/llvm-project/blob/main/llvm/include/ llvm/MC/MCAsmInfoELF.h})。原因是，因为它们必须支持相似的特性，所以使用相同对象文件格式的系统上的系统汇编程序共享相同的特征。我们的目标操作系统是OpenBSD，使用ELF文件格式，因此从MCAsmInfoELF类派生出我们的m88kmccasminfo类。M88kMCAsmInfo.h文件中的声明如下:

\begin{cpp}
namespace llvm {
class Triple;

class M88kMCAsmInfo : public MCAsmInfoELF {
public:
    explicit M88kMCAsmInfo(const Triple &TT);
};
\end{cpp}

\item
M88kMCAsmInfo.cpp文件中的实现只设置两个默认值。目前的两个关键设置是系统使用大端模式和使用|符号进行注释，其他设置用于生成之后的代码:

\begin{cpp}
using namespace llvm;

M88kMCAsmInfo::M88kMCAsmInfo(const Triple &TT) {
    IsLittleEndian = false;
    UseDotAlignForAlignment = true;
    MinInstAlignment = 4;
    CommentString = "|"; // # as comment delimiter is only
                         // allowed at first column
    ZeroDirective = "\t.space\t";
    Data64bitsDirective = "\t.quad\t";
    UsesELFSectionDirectiveForBSS = true;
    SupportsDebugInformation = false;
    ExceptionsType = ExceptionHandling::SjLj;
}
\end{cpp}

\end{enumerate}

现在已经完成了MCAsmInfo类的实现。我们将学习实现的下一个类，可在LLVM中创建指令的二进制表示。

\mySamllsection{为M88k目标实现MCCodeEmitter支持类}

LLVM内部，一条指令由MCInst类的实例表示，指令可以作为汇编文本或以二进制形式发送到目标文件中。M88kMCCodeEmitter类创建指令的二进制表示，而M88kInstPrinter类可生成其文本表示。

首先，将实现M88kMCCodeEmitter类，存储在M88kMCCodeEmitter.cpp文件中:

\begin{enumerate}
\item
类的大部分由TableGen生成，所以只需要添加一些样板代码。注意，没有相应的头文件;工厂函数的原型将添加到M88kMCTargetDesc.h文件中。首先，为生成的指令数量设置一个统计计数器:

\begin{cpp}
using namespace llvm;
#define DEBUG_TYPE "mccodeemitter"
STATISTIC(MCNumEmitted,
          "Number of MC instructions emitted");
\end{cpp}

\item
M88kMCCodeEmitter类位于匿名命名空间中，只需要实现在基类中声明的encodeInstruction()方法和getMachineOpValue()辅助方法。另一个getBinaryCodeForInstr()方法由TableGen根据目标描述生成:

\begin{cpp}
namespace {
class M88kMCCodeEmitter : public MCCodeEmitter {
    const MCInstrInfo &MCII;
    MCContext &Ctx;

public:
    M88kMCCodeEmitter(const MCInstrInfo &MCII,
                      MCContext &Ctx)
    : MCII(MCII), Ctx(Ctx) {}

    ~M88kMCCodeEmitter() override = default;

    void encodeInstruction(
        const MCInst &MI, raw_ostream &OS,
        SmallVectorImpl<MCFixup> &Fixups,
        const MCSubtargetInfo &STI) const override;

    uint64_t getBinaryCodeForInstr(
        const MCInst &MI,
        SmallVectorImpl<MCFixup> &Fixups,
        const MCSubtargetInfo &STI) const;

    unsigned
    getMachineOpValue(const MCInst &MI,
                        const MCOperand &MO,
                        SmallVectorImpl<MCFixup> &Fixups,
                        const MCSubtargetInfo &STI) const;
};
} // end anonymous namespace
\end{cpp}

\item
encodeInstruction()方法只是查找指令的二进制表示，将统计计数器加1，然后以大端格式写出字节。指令有一个固定的4字节大小，所以在端流上使用uint32\_t类型:

\begin{cpp}
void M88kMCCodeEmitter::encodeInstruction(
        const MCInst &MI, raw_ostream &OS,
        SmallVectorImpl<MCFixup> &Fixups,
        const MCSubtargetInfo &STI) const {
    uint64_t Bits =
        getBinaryCodeForInstr(MI, Fixups, STI);
    ++MCNumEmitted;
    support::endian::write<uint32_t>(OS, Bits,
                                     support::big);
}
\end{cpp}

\item
getMachineOpValue()方法的任务是返回操作数的二进制表示形式，我们定义了用于存储在指令中的寄存器的位范围，计算存储在这些地方的值。从生成的代码调用该方法，只支持两种情况。对于寄存器，将返回目标描述中定义的寄存器编码。对于immediate，返回的是立即数:

\begin{cpp}
unsigned M88kMCCodeEmitter::getMachineOpValue(
        const MCInst &MI, const MCOperand &MO,
        SmallVectorImpl<MCFixup> &Fixups,
        const MCSubtargetInfo &STI) const {
    if (MO.isReg())
        return Ctx.getRegisterInfo()->getEncodingValue(
            MO.getReg());
    if (MO.isImm())
        return static_cast<uint64_t>(MO.getImm());
    return 0;
}
\end{cpp}

\item
最后，生成的文件并为类创建一个工厂方法:

\begin{cpp}
#include "M88kGenMCCodeEmitter.inc"

MCCodeEmitter *
llvm::createM88kMCCodeEmitter(const MCInstrInfo &MCII,
                              MCContext &Ctx) {
    return new M88kMCCodeEmitter(MCII, Ctx);
}
\end{cpp}
\end{enumerate}

\mySamllsection{实现了M88k目标的指令输出器支持类}

M88kInstPrinter类具有与M88kMCCodeEmitter类相似的结构，InstPrinter类负责生成LLVM指令的文本表示。类的大部分是由TableGen生成的，但必须添加对输出操作数的支持。该类在M88kInstPrinter.h头文件中声明，实现在M88kInstPrinter.cpp文件中:

\begin{enumerate}
\item
头文件开始。在包含必需的头文件和声明llvm命名空间之后，声明两个前向引用以减少必需的include的数量:

\begin{cpp}
namespace llvm {
class MCAsmInfo;
class MCOperand;
\end{cpp}

\item
除了构造函数，只需要实现printOperand()和printInst()方法。其他方法由TableGen生成:

\begin{cpp}
class M88kInstPrinter : public MCInstPrinter {
public:
    M88kInstPrinter(const MCAsmInfo &MAI,
                    const MCInstrInfo &MII,
                    const MCRegisterInfo &MRI)
        : MCInstPrinter(MAI, MII, MRI) {}

    std::pair<const char *, uint64_t>
    getMnemonic(const MCInst *MI) override;
    void printInstruction(const MCInst *MI,
                            uint64_t Address,
                            const MCSubtargetInfo &STI,
                            raw_ostream &O);

    static const char *getRegisterName(MCRegister RegNo);

    void printOperand(const MCInst *MI, int OpNum,
                        const MCSubtargetInfo &STI,
                        raw_ostream &O);

    void printInst(const MCInst *MI, uint64_t Address,
                    StringRef Annot,
                    const MCSubtargetInfo &STI,
                    raw_ostream &O) override;
};
} // end namespace llvm
\end{cpp}

\item
该实现位于M88kInstPrint.cpp文件中。包含所需的头文件并使用llvm命名空间之后，生成的C++的文件应包含在内:

\begin{cpp}
using namespace llvm;

#define DEBUG_TYPE "asm-printer"

#include "M88kGenAsmWriter.inc"
\end{cpp}

\item
printOperand()方法会检查操作数的类型，并生成一个寄存器名或立即数。通过生成的getRegisterName()方法查找寄存器名:

\begin{cpp}
void M88kInstPrinter::printOperand(
        const MCInst *MI, int OpNum,
        const MCSubtargetInfo &STI, raw_ostream &O) {
    const MCOperand &MO = MI->getOperand(OpNum);
    if (MO.isReg()) {
        if (!MO.getReg())
            O << '0';
        else
            O << '%' << getRegisterName(MO.getReg());
    } else if (MO.isImm())
        O << MO.getImm();
    else
        llvm_unreachable("Invalid operand");
}
\end{cpp}

\item
printInst()方法只调用printInstruction()生成的方法来输出指令，然后使用printAnnotation()方法来输出可能需要的注释:

\begin{cpp}
void M88kInstPrinter::printInst(
        const MCInst *MI, uint64_t Address, StringRef Annot,
        const MCSubtargetInfo &STI, raw_ostream &O) {
    printInstruction(MI, Address, STI, O);
    printAnnotation(O, Annot);
}
\end{cpp}
\end{enumerate}

\mySamllsection{实现特定M88k的目标描述}

M88kMCTargetDesc.cpp文件，可以需要添加一些内容:

\begin{enumerate}
\item
首先，为MCInstPrinter类和MCAsmInfo类创建一个新的工厂方法:

\begin{cpp}
static MCInstPrinter *createM88kMCInstPrinter(
        const Triple &T, unsigned SyntaxVariant,
        const MCAsmInfo &MAI, const MCInstrInfo &MII,
        const MCRegisterInfo &MRI) {
    return new M88kInstPrinter(MAI, MII, MRI);
}

static MCAsmInfo *
        createM88kMCAsmInfo(const MCRegisterInfo &MRI,
        const Triple &TT,
        const MCTargetOptions &Options) {
    return new M88kMCAsmInfo(TT);
}
\end{cpp}

\item
最后，LLVMInitializeM88kTargetMC()函数中，需要添加工厂方法的注册:

\begin{cpp}
extern "C" LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeM88kTargetMC() {
    // …
    TargetRegistry::RegisterMCAsmInfo(
        getTheM88kTarget(), createM88kMCAsmInfo);
    TargetRegistry::RegisterMCCodeEmitter(
        getTheM88kTarget(), createM88kMCCodeEmitter);
    TargetRegistry::RegisterMCInstPrinter(
        getTheM88kTarget(), createM88kMCInstPrinter);
}
\end{cpp}
\end{enumerate}

已经实现了所有必需的支持类，最后可以添加汇编解析器了。

\mySamllsection{创建M88k汇编解析器类}

AsmParser目录中只有一个M88kAsmParser.cpp实现文件。M88kOperand类表示已解析的操作数，由生成的源代码和M88kAssembler类中的汇编解析器实现使用。两个类都在一个匿名命名空间中，只有工厂方法是全局可见的。先来看看M88kOperand类:

\begin{enumerate}
\item
操作数可以是标记、寄存器或立即数。我们定义了OperandKind枚举来区分这些情况，当前类型存储在kind成员中。还存储了操作数的起始和结束位置，用于输出错误信息:

\begin{cpp}
class M88kOperand : public MCParsedAsmOperand {
    enum OperandKind { OpKind_Token, OpKind_Reg,
                        OpKind_Imm };
    OperandKind Kind;
    SMLoc StartLoc, EndLoc;
\end{cpp}

\item
为了存储该值，定义了一个联合。标记存储为StringRef，寄存器由其编号标识。直接对象由MCExpr类表示:

\begin{cpp}
    union {
        StringRef Token;
        unsigned RegNo;
        const MCExpr *Imm;
    };
\end{cpp}

\item
构造函数初始化除了联合之外的所有字段，还定义了返回起始和结束位置值的方法:

\begin{cpp}
public:
    M88kOperand(OperandKind Kind, SMLoc StartLoc,
                SMLoc EndLoc)
        : Kind(Kind), StartLoc(StartLoc), EndLoc(EndLoc) {}
    SMLoc getStartLoc() const override { return StartLoc; }
    SMLoc getEndLoc() const override { return EndLoc; }
\end{cpp}

\item
对于每个操作数类型，必须定义四个方法。对于寄存器，方法是isReg()检查操作数是否为寄存器，getReg()返回值，createReg()创建寄存器操作数，以及addRegOperands()为指令添加操作数。后一个函数由生成的源代码在构造指令时调用。标记和立即数的方法类似:

\begin{cpp}
bool isReg() const override {
    return Kind == OpKind_Reg;
}

unsigned getReg() const override { return RegNo; }

static std::unique_ptr<M88kOperand>
createReg(unsigned Num, SMLoc StartLoc,
          SMLoc EndLoc) {
    auto Op = std::make_unique<M88kOperand>(
        OpKind_Reg, StartLoc, EndLoc);
    Op->RegNo = Num;
    return Op;
}

void addRegOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "Invalid number of operands");
    Inst.addOperand(MCOperand::createReg(getReg()));
}
\end{cpp}

\item
最后，超类定义了一个需要实现的print()虚函数。这只用于调试目的:

\begin{cpp}
void print(raw_ostream &OS) const override {
    switch (Kind) {
        case OpKind_Imm:
            OS << "Imm: " << getImm() << "\n"; break;
        case OpKind_Token:
            OS << "Token: " << getToken() << "\n"; break;
        case OpKind_Reg:
            OS << "Reg: "
               << M88kInstPrinter::getRegisterName(getReg())
               << "\n"; break;
        }
    }
};
\end{cpp}

\end{enumerate}

接下来，声明M88kAsmParser类。匿名命名空间将在声明后结束:

\begin{enumerate}
\item
类的开始，将包含生成的代码段:

\begin{cpp}
class M88kAsmParser : public MCTargetAsmParser {
#define GET_ASSEMBLER_HEADER
#include "M88kGenAsmMatcher.inc"
\end{cpp}

\item
接下来，定义必需的字段。需要一个对实际解析器的引用，其属于MCAsmParser类，以及对子目标信息的引用:

\begin{cpp}
    MCAsmParser &Parser;
    const MCSubtargetInfo &SubtargetInfo;
\end{cpp}

\item
为了实现汇编器，我们覆写了MCTargetAsmParser超类中定义的两个方法。MatchAndEmitInstruction()尝试匹配一条指令，并发出由MCInst类的实例表示的指令。解析指令是在ParseInstruction()方法中完成的，而parseRegister()和tryParseRegister()方法负责解析寄存器:

\begin{cpp}
    bool
    ParseInstruction(ParseInstructionInfo &Info,
                    StringRef Name, SMLoc NameLoc,
                    OperandVector &Operands) override;
    bool parseRegister(MCRegister &RegNo, SMLoc &StartLoc,
                        SMLoc &EndLoc) override;
    OperandMatchResultTy
    tryParseRegister(MCRegister &RegNo, SMLoc &StartLoc,
                    SMLoc &EndLoc) override;
    bool parseRegister(MCRegister &RegNo, SMLoc &StartLoc,
                        SMLoc &EndLoc,
                        bool RestoreOnFailure);
    bool parseOperand(OperandVector &Operands,
                        StringRef Mnemonic);
    bool MatchAndEmitInstruction(
        SMLoc IdLoc, unsigned &Opcode,
        OperandVector &Operands, MCStreamer &Out,
        uint64_t &ErrorInfo,
        bool MatchingInlineAsm) override;
\end{cpp}

\item
内联构造函数，主要初始化所有字段。这样就完成了类声明，之后匿名命名空间也结束了:

\begin{cpp}
public:
    M88kAsmParser(const MCSubtargetInfo &STI,
                    MCAsmParser &Parser,
                    const MCInstrInfo &MII,
                    const MCTargetOptions &Options)
        : MCTargetAsmParser(Options, STI, MII),
            Parser(Parser), SubtargetInfo(STI) {
        setAvailableFeatures(ComputeAvailableFeatures(
            SubtargetInfo.getFeatureBits()));
    }
};
\end{cpp}

\item
包含汇编程序的生成部分:

\begin{cpp}
#define GET_REGISTER_MATCHER
#define GET_MATCHER_IMPLEMENTATION
#include "M88kGenAsmMatcher.inc"
\end{cpp}

\item
每当需要一条指令时，就调用ParseInstruction()方法，必须能够解析指令的所有语法形式。目前，只有接受三个操作数的指令，用逗号分隔，解析起来很简单。注意，若出现错误，返回值为true !

\begin{cpp}
bool M88kAsmParser::ParseInstruction(
        ParseInstructionInfo &Info, StringRef Name,
        SMLoc NameLoc, OperandVector &Operands) {
    Operands.push_back(
        M88kOperand::createToken(Name, NameLoc));
    if (getLexer().isNot(AsmToken::EndOfStatement)) {
        if (parseOperand(Operands, Name)) {
            return Error(getLexer().getLoc(),
                         "expected operand");
        }
        while (getLexer().is(AsmToken::Comma)) {
            Parser.Lex();
            if (parseOperand(Operands, Name)) {
                return Error(getLexer().getLoc(),
                "expected operand");
            }
        }
        if (getLexer().isNot(AsmToken::EndOfStatement))
            return Error(getLexer().getLoc(),
                "unexpected token in argument list");
    }
    Parser.Lex();
    return false;
}
\end{cpp}

\item
操作数可以是寄存器或立即寄存器。泛化一个位并解析一个表达式，而不仅仅是一个整数。这有助于以后添加地址模式。若成功，解析后的操作数可添加到操作数列表中:

\begin{cpp}
bool M88kAsmParser::parseOperand(
        OperandVector &Operands, StringRef Mnemonic) {
    if (Parser.getTok().is(AsmToken::Percent)) {
        MCRegister RegNo;
        SMLoc StartLoc, EndLoc;
        if (parseRegister(RegNo, StartLoc, EndLoc,
                        /*RestoreOnFailure=*/false))
            return true;
        Operands.push_back(M88kOperand::createReg(
            RegNo, StartLoc, EndLoc));
        return false;
    }

    if (Parser.getTok().is(AsmToken::Integer)) {
        SMLoc StartLoc = Parser.getTok().getLoc();
        const MCExpr *Expr;
        if (Parser.parseExpression(Expr))
            return true;
        SMLoc EndLoc = Parser.getTok().getLoc();
        Operands.push_back(
            M88kOperand::createImm(Expr, StartLoc, EndLoc));
        return false;
    }
    return true;
}
\end{cpp}

\item
parseRegister()方法尝试解析一个寄存器。首先，检查百分号\%。若后面跟着一个与寄存器名匹配的标识符，就成功地解析了一个寄存器，并在RegNo参数中返回寄存器号。若不能识别一个寄存器，若RestoreOnFailure参数为true，就可能要撤销词法分析:

\begin{cpp}
bool M88kAsmParser::parseRegister(
        MCRegister &RegNo, SMLoc &StartLoc, SMLoc &EndLoc,
        bool RestoreOnFailure) {
    StartLoc = Parser.getTok().getLoc();

    if (Parser.getTok().isNot(AsmToken::Percent))
        return true;
    const AsmToken &PercentTok = Parser.getTok();
    Parser.Lex();

    if (Parser.getTok().isNot(AsmToken::Identifier) ||
        (RegNo = MatchRegisterName(
            Parser.getTok().getIdentifier())) == 0) {
        if (RestoreOnFailure)
            Parser.getLexer().UnLex(PercentTok);
        return Error(StartLoc, "invalid register");
    }
    Parser.Lex();
    EndLoc = Parser.getTok().getLoc();
    return false;
}
\end{cpp}

\item
parseRegister()和tryparseRegister()覆写的方法只是之前定义的方法的包装。后一种方法，还将布尔值返回值转换为OperandMatchResultTy枚举的枚举成员:

\begin{cpp}
bool M88kAsmParser::parseRegister(MCRegister &RegNo,
                                    SMLoc &StartLoc,
                                    SMLoc &EndLoc) {
    return parseRegister(RegNo, StartLoc, EndLoc,
                         /*RestoreOnFailure=*/false);
}

OperandMatchResultTy M88kAsmParser::tryParseRegister(
        MCRegister &RegNo, SMLoc &StartLoc, SMLoc &EndLoc) {
    bool Result =
        parseRegister(RegNo, StartLoc, EndLoc,

    /*RestoreOnFailure=*/true);
    bool PendingErrors = getParser().hasPendingError();
    getParser().clearPendingErrors();
    if (PendingErrors)
        return MatchOperand_ParseFail;
    if (Result)
        return MatchOperand_NoMatch;
    return MatchOperand_Success;
}
\end{cpp}

\item
最后，MatchAndEmitInstruction()方法驱动解析，该方法的大部分用于发出错误消息。为了识别指令，调用MatchInstructionImpl()生成的方法:

\begin{cpp}
bool M88kAsmParser::MatchAndEmitInstruction(
        SMLoc IdLoc, unsigned &Opcode,
        OperandVector &Operands, MCStreamer &Out,
        uint64_t &ErrorInfo, bool MatchingInlineAsm) {
    MCInst Inst;
    SMLoc ErrorLoc;

    switch (MatchInstructionImpl(
            Operands, Inst, ErrorInfo, MatchingInlineAsm)) {
    case Match_Success:
        Out.emitInstruction(Inst, SubtargetInfo);
        Opcode = Inst.getOpcode();
        return false;
    case Match_MissingFeature:
        return Error(IdLoc, "Instruction use requires "
                     "option to be enabled");
    case Match_MnemonicFail:
        return Error(IdLoc,
                     "Unrecognized instruction mnemonic");
    case Match_InvalidOperand: {
        ErrorLoc = IdLoc;
        if (ErrorInfo != ~0U) {
            if (ErrorInfo >= Operands.size())
                return Error(
                    IdLoc, "Too few operands for instruction");
            ErrorLoc = ((M88kOperand &)*Operands[ErrorInfo])
                    .getStartLoc();
            if (ErrorLoc == SMLoc())
                ErrorLoc = IdLoc;
        }
        return Error(ErrorLoc,
                     "Invalid operand for instruction");
    }
    default:
        break;
    }
    llvm_unreachable("Unknown match type detected!");
}
\end{cpp}

\item
和其他类一样，汇编解析器也有自己的工厂方法:

\begin{cpp}
extern "C" LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeM88kAsmParser() {
    RegisterMCAsmParser<M88kAsmParser> X(
        getTheM88kTarget());
}
\end{cpp}
\end{enumerate}

这就完成了汇编解析器的实现。构建LLVM之后，可以使用llvm-mc机器码工具组装一条汇编指令:

\begin{shell}
$ echo 'and %r1,%r2,%r3' | \
    bin/llvm-mc --triple m88k-openbsd --show-encoding
        .text
        and %r1, %r2, %r3 | encoding: [0xf4,0x22,0x40,0x03]
\end{shell}

注意，使用竖条|作为注释符号。这是我们在M88kMCAsmInfo类中配置的值。

\begin{myTip}{调试汇编匹配器}
要调试汇编器匹配器，可以指定-{}-debug-only=asm-matcher命令行选项。这有助于理解，为什么解析的指令不能与目标描述中定义的指令匹配。
\end{myTip}

下一节中，我们将向llvm-mc工具添加反汇编器特性。































